// Based on: https://github.com/maxatwork/form2js

/**
 * Returns form values represented as Javascript object
 * "name" attribute defines structure of resulting object
 *
 * @param rootNode {Element|String} root form element (or it's id) or array of root elements
 * @param delimiter {String} structure parts delimiter defaults to '.'
 * @param skipEmpty {Boolean} should skip empty text values, defaults to true
 * @param nodeCallback {Function} custom function to get node value
 * @param useIdIfEmptyName {Boolean} if true value of id attribute of field will be used if name of field is empty
 */
export default function getFormData(rootNode) {
  const delimiter = '.'
  const getDisabled = false
  const skipEmpty = false
  const nodeCallback = null
  const useIdIfEmptyName = false

  rootNode =
    typeof rootNode == 'string' ? document.getElementById(rootNode) : rootNode

  var formValues = [],
    currNode,
    i = 0

  /* If rootNode is array - combine values */
  if (
    rootNode.constructor == Array ||
    (typeof NodeList != 'undefined' && rootNode.constructor == NodeList)
  ) {
    while ((currNode = rootNode[i++])) {
      formValues = formValues.concat(
        getFormValues(currNode, nodeCallback, useIdIfEmptyName, getDisabled)
      )
    }
  } else {
    formValues = getFormValues(
      rootNode,
      nodeCallback,
      useIdIfEmptyName,
      getDisabled
    )
  }

  return processNameValues(formValues, skipEmpty, delimiter)
}

/**
 * Processes collection of { name: 'name', value: 'value' } objects.
 * @param nameValues
 * @param skipEmpty if true skips elements with value == '' or value == null
 * @param delimiter
 */
function processNameValues(nameValues, skipEmpty, delimiter) {
  var result = {},
    arrays = {},
    i,
    j,
    k,
    l,
    value,
    nameParts,
    currResult,
    arrNameFull,
    arrName,
    arrIdx,
    namePart,
    name,
    _nameParts

  for (i = 0; i < nameValues.length; i++) {
    value = nameValues[i].value

    if (skipEmpty && (value === '' || value === null)) continue

    name = nameValues[i].name
    _nameParts = name.split(delimiter)
    nameParts = []
    currResult = result
    arrNameFull = ''

    for (j = 0; j < _nameParts.length; j++) {
      namePart = _nameParts[j].split('][')
      if (namePart.length > 1) {
        for (k = 0; k < namePart.length; k++) {
          if (k == 0) {
            namePart[k] = namePart[k] + ']'
          } else if (k == namePart.length - 1) {
            namePart[k] = '[' + namePart[k]
          } else {
            namePart[k] = '[' + namePart[k] + ']'
          }

          arrIdx = namePart[k].match(/([a-z_]+)?\[([a-z_][a-z0-9_]+?)\]/i)
          if (arrIdx) {
            for (l = 1; l < arrIdx.length; l++) {
              if (arrIdx[l]) nameParts.push(arrIdx[l])
            }
          } else {
            nameParts.push(namePart[k])
          }
        }
      } else nameParts = nameParts.concat(namePart)
    }

    for (j = 0; j < nameParts.length; j++) {
      namePart = nameParts[j]

      if (namePart.indexOf('[]') > -1 && j == nameParts.length - 1) {
        arrName = namePart.substr(0, namePart.indexOf('['))
        arrNameFull += arrName

        if (!currResult[arrName]) currResult[arrName] = []
        currResult[arrName].push(value)
      } else if (namePart.indexOf('[') > -1) {
        arrName = namePart.substr(0, namePart.indexOf('['))
        arrIdx = namePart.replace(/(^([a-z_]+)?\[)|(\]$)/gi, '')

        /* Unique array name */
        arrNameFull += '_' + arrName + '_' + arrIdx

        /*
         * Because arrIdx in field name can be not zero-based and step can be
         * other than 1, we can't use them in target array directly.
         * Instead we're making a hash where key is arrIdx and value is a reference to
         * added array element
         */

        if (!arrays[arrNameFull]) arrays[arrNameFull] = {}
        if (arrName != '' && !currResult[arrName]) currResult[arrName] = []

        if (j == nameParts.length - 1) {
          if (arrName == '') {
            currResult.push(value)
            arrays[arrNameFull][arrIdx] = currResult[currResult.length - 1]
          } else {
            currResult[arrName].push(value)
            arrays[arrNameFull][arrIdx] =
              currResult[arrName][currResult[arrName].length - 1]
          }
        } else {
          if (!arrays[arrNameFull][arrIdx]) {
            if (/^[0-9a-z_]+\[?/i.test(nameParts[j + 1]))
              currResult[arrName].push({})
            else currResult[arrName].push([])

            arrays[arrNameFull][arrIdx] =
              currResult[arrName][currResult[arrName].length - 1]
          }
        }

        currResult = arrays[arrNameFull][arrIdx]
      } else {
        arrNameFull += namePart

        if (j < nameParts.length - 1) {
          /* Not the last part of name - means object */
          if (!currResult[namePart]) currResult[namePart] = {}
          currResult = currResult[namePart]
        } else {
          currResult[namePart] = value
        }
      }
    }
  }

  return result
}

function getFormValues(rootNode, nodeCallback, useIdIfEmptyName, getDisabled) {
  var result = extractNodeValues(
    rootNode,
    nodeCallback,
    useIdIfEmptyName,
    getDisabled
  )
  return result.length > 0
    ? result
    : getSubFormValues(rootNode, nodeCallback, useIdIfEmptyName, getDisabled)
}

function getSubFormValues(
  rootNode,
  nodeCallback,
  useIdIfEmptyName,
  getDisabled
) {
  var result = [],
    currentNode = rootNode.firstChild

  while (currentNode) {
    result = result.concat(
      extractNodeValues(
        currentNode,
        nodeCallback,
        useIdIfEmptyName,
        getDisabled
      )
    )
    currentNode = currentNode.nextSibling
  }

  return result
}

function extractNodeValues(node, nodeCallback, useIdIfEmptyName, getDisabled) {
  if (node.disabled && !getDisabled) return []

  var callbackResult,
    fieldValue,
    result,
    fieldName = getFieldName(node, useIdIfEmptyName)

  callbackResult = nodeCallback && nodeCallback(node)

  if (callbackResult && callbackResult.name) {
    result = [callbackResult]
  } else if (fieldName != '' && node.nodeName.match(/INPUT|TEXTAREA/i)) {
    fieldValue = getFieldValue(node, getDisabled)
    if (null === fieldValue) {
      result = []
    } else {
      result = [{ name: fieldName, value: fieldValue }]
    }
  } else if (fieldName != '' && node.nodeName.match(/SELECT/i)) {
    fieldValue = getFieldValue(node, getDisabled)
    result = [{ name: fieldName.replace(/\[\]$/, ''), value: fieldValue }]
  } else {
    result = getSubFormValues(node, nodeCallback, useIdIfEmptyName, getDisabled)
  }

  return result
}

function getFieldName(node, useIdIfEmptyName) {
  if (node.name && node.name != '') return node.name
  else if (useIdIfEmptyName && node.id && node.id != '') return node.id
  else return ''
}

function getFieldValue(fieldNode, getDisabled) {
  if (fieldNode.disabled && !getDisabled) return null

  switch (fieldNode.nodeName) {
    case 'INPUT':
    case 'TEXTAREA':
      switch (fieldNode.type.toLowerCase()) {
        case 'radio':
          if (fieldNode.checked && fieldNode.value === 'false') return false
        // fallthrough
        case 'checkbox':
          if (fieldNode.checked && fieldNode.value === 'true') return true
          if (!fieldNode.checked && fieldNode.value === 'true') return false
          if (fieldNode.checked) return fieldNode.value
          break

        case 'button':
        case 'reset':
        case 'submit':
        case 'image':
          return ''

        default:
          return fieldNode.value
      }
      break

    case 'SELECT':
      return getSelectedOptionValue(fieldNode)

    default:
      break
  }

  return null
}

function getSelectedOptionValue(selectNode) {
  var multiple = selectNode.multiple,
    result = [],
    options,
    i,
    l

  if (!multiple) return selectNode.value

  for (
    options = selectNode.getElementsByTagName('option'),
      i = 0,
      l = options.length;
    i < l;
    i++
  ) {
    if (options[i].selected) result.push(options[i].value)
  }

  return result
}
